package priv.pfz.basedao.sharding;

import com.google.common.collect.Sets;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import priv.pfz.basedao.Utils;
import priv.pfz.basedao.info.FieldInfo;
import priv.pfz.basedao.info.DaoInfo;
import priv.pfz.basedao.query.BaseQuery;
import priv.pfz.basedao.query.Condition;

import java.util.Set;

/**
 * 切分管理器
 * 1、manualInfo为手工指定的切分信息
 * 2、SQL中切分键每个确定的值都产生一个切分信息
 * 3、指定切分信息和自动切分信息必须全部一致
 *
 * @author yyjxp
 * 2024/7/31 20:26
 */
public class ShardingManager {
    private final DaoInfo daoInfo;
    private final ShardingInfo shardingInfo;
    private final Set<String> dbSuffixSet = Sets.newHashSet();
    private final Set<String> tableSuffixSet = Sets.newHashSet();
    @Getter
    private String thirdShardingStr = "";

    public ShardingManager(DaoInfo daoInfo, ShardingInfo shardingInfo) {
        this.daoInfo = daoInfo;
        this.shardingInfo = shardingInfo;
        checkValid();
        handleManualInfo(shardingInfo);
    }

    private boolean isShardingDb() {
        return daoInfo.getShardingMode() == ShardingMode.SHARDING_DB
                || daoInfo.getShardingMode() == ShardingMode.SHARDING_DB_TABLE;
    }

    private boolean isShardingTable() {
        return daoInfo.getShardingMode() == ShardingMode.SHARDING_TABLE
                || daoInfo.getShardingMode() == ShardingMode.SHARDING_DB_TABLE;
    }

    private boolean isAuto() {
        return daoInfo.getShardingMode() == ShardingMode.SHARDING_DB
                || daoInfo.getShardingMode() == ShardingMode.SHARDING_TABLE
                || daoInfo.getShardingMode() == ShardingMode.SHARDING_DB_TABLE;
    }

    private void checkValid() {
        if (isShardingDb() && StringUtils.isBlank(daoInfo.getDbName())) {
            throw new RuntimeException("分库模式和分库分表模式必须指定数据库名称");
        }
    }

    private void handleManualInfo(ShardingInfo info) {
        if (info == null) {
            return;
        }
        if (daoInfo.getShardingMode() == ShardingMode.NO_SHARDING) {
            return;
        }
        if (daoInfo.getShardingMode() == ShardingMode.THIRD_SHARDING_COMPONENT) {
            if (StringUtils.isNotBlank(info.getThirdShardingStr())) {
                thirdShardingStr = " " + info.getThirdShardingStr();
            }
            return;
        }
        if (isShardingDb()) {
            if (StringUtils.isNotBlank(info.getDbSuffix())) {
                dbSuffixSet.add(info.getDbSuffix());
            }
        }
        if (isShardingTable()) {
            if (StringUtils.isNotBlank(info.getTableSuffix())) {
                tableSuffixSet.add(info.getTableSuffix());
            }
        }
    }

    public void addShardingInfo(FieldInfo fieldInfo, Object value) {
        if (fieldInfo.getShardingStrategy() == null) {
            return;
        }
        if (!isAuto()) {
            return;
        }
        Utils.checkNotNull(value, "切分键值不能为空");
        ShardingInfo shardingInfo = fieldInfo.getShardingStrategy().getShardingInfo(value);
        handleAutoInfo(shardingInfo);
    }

    public void addShardingInfos(BaseQuery<?> query) {
        FieldInfo fieldInfo = daoInfo.getShardingKeyFieldInfo();
        if (fieldInfo == null) {
            return;
        }
        if (!isAuto()) {
            return;
        }
        //找出切分键字段且是精确的条件，计算分库分表规则
        for (Condition<?> condition : query.getConditions()) {
            if (!condition.getFieldName().equals(fieldInfo.getFieldName())) {
                continue;
            }
            if (!condition.getOpType().isAccurate()) {
                continue;
            }
            for (Object value : condition.getValues()) {
                ShardingInfo shardingInfo = fieldInfo.getShardingStrategy().getShardingInfo(value);
                handleAutoInfo(shardingInfo);
            }
        }
    }

    private void handleAutoInfo(ShardingInfo info) {
        Utils.checkNotNull(info, "切分策略输出的切分信息为空");
        if (isShardingDb()) {
            Utils.checkNotBlank(info.getDbSuffix(), "切分策略输出的分库后缀为空");
            dbSuffixSet.add(info.getDbSuffix());
            Utils.checkArgument(dbSuffixSet.size() <= 1, "分库后缀存在冲突:" + dbSuffixSet);
        }
        if (isShardingTable()) {
            Utils.checkNotBlank(info.getTableSuffix(), "切分策略输出的分表后缀为空");
            tableSuffixSet.add(info.getTableSuffix());
            Utils.checkArgument(tableSuffixSet.size() <= 1, "分表后缀存在冲突:" + tableSuffixSet);
        }
    }

    public String getShardingTable() {
        String dbSuffix = null;
        String tableSuffix = null;
        if (isShardingDb()) {
            Utils.checkArgument(dbSuffixSet.size() == 1, "缺少人工指定或自动切分的分库后缀");
            dbSuffix = dbSuffixSet.iterator().next();
            shardingInfo.setDbSuffix(dbSuffix);
        }
        if (isShardingTable()) {
            Utils.checkArgument(tableSuffixSet.size() == 1, "缺少人工指定或自动切分的分表后缀");
            tableSuffix = tableSuffixSet.iterator().next();
            shardingInfo.setTableSuffix(tableSuffix);
        }
        return getTable(dbSuffix, tableSuffix);
    }

    /**
     * `tableName`
     * `tableName_tableSuffix`
     * dbName.`tableName`
     * dbName.`tableName_tableSuffix`
     * dbName_dbSuffix.`tableName`
     * dbName_dbSuffix.`tableName_tableSuffix`
     */
    private String getTable(String dbSuffix, String tableSuffix) {
        String dbName = getDbName(daoInfo.getDbName(), dbSuffix);
        String tableName = getTableName(daoInfo.getTableName(), tableSuffix);
        if (StringUtils.isBlank(dbName)) {
            return "`" + tableName + "`";
        } else {
            return dbName + ".`" + tableName + "`";
        }
    }

    private String getDbName(String dbName, String dbSuffix) {
        if (StringUtils.isAllBlank(dbName, dbSuffix)) {
            return null;
        }
        if (StringUtils.isBlank(dbName)) {
            throw new RuntimeException("未指定数据库名称,无法使用分库功能");
        }
        if (StringUtils.isBlank(dbSuffix)) {
            return dbName;
        }
        return dbName + "_" + dbSuffix;
    }

    private String getTableName(String tableName, String tableSuffix) {
        if (StringUtils.isBlank(tableSuffix)) {
            return tableName;
        }
        return tableName + "_" + tableSuffix;
    }
}
